package com.gitee.slowcreator.http.enhance;

import cn.hutool.core.util.StrUtil;
import com.gitee.slowcreator.http.annotation.*;
import com.gitee.slowcreator.http.constant.ScParamsTypeConstant;
import com.gitee.slowcreator.http.enhance.around.EnhanceAfter;
import com.gitee.slowcreator.http.enhance.around.EnhanceBefore;
import com.gitee.slowcreator.http.enhance.around.EnhanceFail;
import com.gitee.slowcreator.http.exception.EnhanceException;
import com.gitee.slowcreator.http.exception.FailThrowException;
import com.gitee.slowcreator.http.executor.HttpExecutor;
import com.gitee.slowcreator.http.executor.HttpExecutorFactory;
import com.gitee.slowcreator.http.params.*;
import com.gitee.slowcreator.http.retry.HttpRetry;
import com.gitee.slowcreator.http.retry.HttpRetryStrategy;
import com.gitee.slowcreator.http.retry.RetryExecutor;
import com.gitee.slowcreator.http.util.ScResultUtil;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;

/**
 * @author zxw
 * @date 2023/1/7 上午9:33
 */
@Slf4j
public class HttpRequestHandle<T> implements InvocationHandler {

    private final Class<T> proxyInterface;

    public HttpRequestHandle(Class<T> proxyInterface) {
        this.proxyInterface = proxyInterface;
    }

    /**
     * 增强后的结果返回 返回与传入的是不是同一个对象要看增强过程有没有替换
     * 如果需要改变返回的参数类型也可以 接口的返回值不一定是默认的 {@link com.gitee.slowcreator.http.params.ScReturn}
     * 返回的最后类型由最后一个 EnhanceAfter决定，可以是任意类型 接口与返回类型对应就行
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        HttpClient httpClient;
        httpClient = method.getAnnotation(HttpClient.class);
        if (httpClient == null) {
            throw new NullPointerException("请求注解不能为空！");
        }

        ScReturn scReturn = null;
        try {

            Class<? extends EnhanceBefore> enhanceBeforeClass = httpClient.enhanceBefore();
            try {
                EnhanceBefore enhanceBefore = enhanceBeforeClass.newInstance();
                enhanceBefore.before(method, args);
            } catch (InstantiationException | IllegalAccessException e) {
                log.error("生成增强后置实例失败：", e);
                throw new EnhanceException("生成增强后置实例失败:", e);
            }
            // 开始请求
            scReturn = this.request(method, args, httpClient);
        } catch (Throwable ex) {
            log.error("请求发生异常:", ex);
            scReturn = new ScReturn();
            scReturn.setException(true);
            scReturn.setSuccess(false);
            scReturn.setData(ex.getMessage().getBytes(StandardCharsets.UTF_8));
        }

        this.failThrowExceptionIfNecessary(scReturn, httpClient);
        return scReturn;
    }

    /**
     * 失败抛出异常 如果需要的话
     *
     * @param request
     * @param httpClient
     */
    private void failThrowExceptionIfNecessary(ScReturn request, HttpClient httpClient) {

        boolean success = ScResultUtil.requestSuccess(request);
        if (!success && httpClient.failThrow()) {
            Class<? extends RuntimeException> clazz = httpClient.throwException();
            try {
                throw clazz.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                log.error("失败抛出异常失败,抛出默认异常FailThrowException:\n", e);
                throw new FailThrowException(e);
            }
        }
    }

    private ScReturn request(Method method, Object[] args, HttpClient httpClient) {

        Retry retry = httpClient.retry();
        RetryExecutor<ScReturn> requestExecutor = (e) -> {

            ScReturn returnData = this.doRequest(method, args, httpClient);
            if (returnData == null || !returnData.isSuccess()) {
                // 请求失败 返回false
                return new RetryHandleResult<>(returnData, true, false);
            } else {
                // 请求成功 调用后置处理 如果后置处理失败 比如返回值失败啥的还会再次调用
                Class<? extends EnhanceAfter> enhanceAfterClass = httpClient.enhanceAfter();
                // 说明执行成功
                try {
                    EnhanceAfter enhanceAfter = enhanceAfterClass.newInstance();
                    ScReturn after = enhanceAfter.after(returnData);
                    // 这里不用判断after.isSuccess() 之前就是成功的，这里加个 after == null 是为了避免后置处理替换了参数
                    if (after == null || after.getParseData() != null && !after.getParseData().isParseSuccess()) {
                        // 解析失败了  再次调用
                        return new RetryHandleResult<>(returnData, true, false);
                    } else {
                        // 请求成功 不用判断重试
                        return new RetryHandleResult<>(after, false, true);
                    }
                } catch (InstantiationException | IllegalAccessException ex) {
                    log.error("生成增强后置实例失败：", ex);
                    // 生成EnhanceFail失败 直接抛出异常 重试没意义
                    throw new EnhanceException("生成增强后置实例失败:", ex);
                }
            }
        };

        HttpRetry<ScReturn> httpRetry = new HttpRetry<>(this.retryAnno2RetryStrategy(retry));
        RetryHandleResult<ScReturn> handle = httpRetry.handle(requestExecutor);
        if (handle.isSuccess()) {
            // 请求成功 直接返回
            return handle.getReturnData();
        } else {
            // 说明执行失败
            Class<? extends EnhanceFail> enhanceFailClass = httpClient.enhanceFail();
            // 执行失败增强方法
            EnhanceFail enhanceFail;
            try {
                enhanceFail = enhanceFailClass.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                log.error("生成增强失败实例失败：", e);
                throw new EnhanceException("生成增强失败实例失败:", e);
            }
            return enhanceFail.fail(handle.getReturnData());
        }
    }

    private HttpRetryStrategy retryAnno2RetryStrategy(Retry retry) {

        return HttpRetryStrategy.builder()
                .noRetryFor(retry.noRetryFor())
                .retryFor(retry.retryFor())
                .traverseCauses(retry.traverseCauses())
                .retryTimes(retry.retryTimes())
                .open(retry.open())
                .build();

    }

    private ScReturn doRequest(Method method, Object[] args, HttpClient httpClient) {

        HttpExecutor httpExecutor = HttpExecutorFactory.get();
        ScUnionParams scUnionParams = this.getRequestParams(httpClient, method, args);

        return httpExecutor.doHttpRequest(httpClient, scUnionParams);
    }

    /**
     * 获取请求参数
     *
     * @param httpClient 基础信息注解
     * @param method     方法
     * @param args       参数
     * @return
     */
    @SuppressWarnings("unchecked")
    private ScUnionParams getRequestParams(HttpClient httpClient, Method method, Object[] args) {

        ScUnionParams executeUnionParams = (ScUnionParams) Arrays.stream(args)
                .filter(e -> e instanceof ScUnionParams)
                .findFirst()
                .orElse(null);

        // 如果用户传入的是 unionParams 非空直接返回 不为空 通过注解或者类型查询
        if (executeUnionParams != null) {
            return executeUnionParams;
        }

        executeUnionParams = new ScUnionParams();

        Object params = getParamsOrAnnotationParams(
                method.getParameterAnnotations(),
                args,
                ScParamsTypeConstant.HEADER,
                HttpHeader.class);
        executeUnionParams.setHttpHeader((Map<String, String>) params);

        params = getParamsOrAnnotationParams(
                method.getParameterAnnotations(),
                args,
                ScParamsTypeConstant.BODY,
                HttpBody.class);
        executeUnionParams.setHttpBody((Map<String, Object>) params);

        params = getParamsOrAnnotationParams(
                method.getParameterAnnotations(),
                args,
                ScParamsTypeConstant.PARAMS,
                HttpParams.class);
        executeUnionParams.setHttpParams((Map<String, String[]>) params);

        params = getParamsOrAnnotationParams(
                method.getParameterAnnotations(),
                args,
                ScParamsTypeConstant.RETURN_HEADER,
                HttpReturnHeader.class);
        executeUnionParams.setReturnHeader((Set<String>) params);

        if (httpClient.isMultipart()) {
            Object multipart = Arrays.stream(args).filter(e -> e instanceof ScMultipart).findFirst().orElse(null);
            executeUnionParams.setScMultipart((ScMultipart) multipart);
        }

        // 查询url
        String httpUrl = (String) queryParamsByAnnotation(method.getParameterAnnotations(), args, HttpUrl.class);
        executeUnionParams.setHttpUrl(httpUrl);

        return executeUnionParams;
    }

    /**
     * 获取参数 或者注解参数 比如 ScHeader 可以直接传入 ScHeader 或者传入对应的类型 ScHeader对应类型 Map<String, String>
     * 如果两个都存在 则忽略注解参数
     *
     * @param annotations
     * @param args
     * @param params
     * @param annotationClass
     * @return
     */
    private static Object getParamsOrAnnotationParams(Annotation[][] annotations,
                                                      Object[] args,
                                                      String params,
                                                      Class<? extends Annotation> annotationClass) {

        for (Object arg : args) {
            if (arg instanceof ScBase) {
                ScBase base = (ScBase) arg;
                String type = base.type();
                if (StrUtil.equals(type, params)) {
                    return arg;
                }
            }
        }

        return queryParamsByAnnotation(annotations, args, annotationClass);
    }

    /**
     * 根据注解查询参数
     *
     * @param annotations
     * @param args
     * @param annotationClass
     * @return
     */
    private static Object queryParamsByAnnotation(Annotation[][] annotations,
                                                  Object[] args,
                                                  Class<? extends Annotation> annotationClass) {

        for (int i = 0; i < annotations.length; i++) {
            Annotation[] paramAnn = annotations[i];
            for (Annotation annotation : paramAnn) {
                //这里判断当前注解是否为所需注解
                if (annotation.annotationType().equals(annotationClass)) {

                    return args[i];
                }
            }
        }

        return null;
    }
}
